﻿// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'

///-----------------------------------------------------------------
/// Shader:             RayCast
/// Description:        Volumetric ray-casting for VolumeComponents.
/// Author:             LISCINTEC
///                         http://www.liscintec.com
///                         info@liscintec.com
/// Date:               Feb 2017
/// Notes:              -
/// Revision History:   First release
/// 
/// This file is part of the Volume Viewer Pro Package.
/// Volume Viewer Pro is a Unity Asset Store product.
/// https://www.assetstore.unity3d.com/#!/content/83185
///-----------------------------------------------------------------

Shader "Hidden/VolumeViewer/RayCast"
{
	Properties
	{
		[HideInInspector] rayOffset2D("rayOffset2D", 2D) = "gray" {}
	}
	SubShader
	{
		Tags { "LightMode" = "ForwardBase" "VolumeTag" = "Volume" }
		Pass
		{
			Cull Front

			CGPROGRAM
				#pragma target 3.0
				#pragma vertex vert
				#pragma fragment frag

				#pragma exclude_renderers d3d9

				#pragma multi_compile _ VOLUMEVIEWER_RGBA_DATA VOLUMEVIEWER_TF_DATA VOLUMEVIEWER_TF_DATA_2D
				#pragma multi_compile __ VOLUMEVIEWER_RGBA_OVERLAY VOLUMEVIEWER_TF_OVERLAY 
				#pragma multi_compile ___ VOLUMEVIEWER_CULLING VOLUMEVIEWER_INVERT_CULLING
				#pragma multi_compile ____ VOLUMEVIEWER_OVERLAY_VOIDS_CULLING
				#pragma multi_compile _____ VOLUMEVIEWER_DEPTH_TEST
				#pragma multi_compile ______ VOLUMEVIEWER_LIGHT VOLUMEVIEWER_TF_LIGHT

				#define EPSILON_FLOAT	0.0001
				#define EPSILON_HALF	0.001
				#define SKIP_INTENSITY	0.01

				//#define VOLUMEVIEWER_GRADIENT32 1
				//#define VOLUMEVIEWER_GRADIENT54 1

				struct u2v {
					float4 	pos : POSITION;
					half2 	uv 	: TEXCOORD0;
				};

				struct v2f {
					float4 	pos 	: SV_POSITION;
					float3	osPos 	: TEXCOORD0;
					half2 	uv		: TEXCOORD1;
				};

				uniform float focalLength;
				uniform float nearClipPlane;
				sampler3D data3D;
				uniform half4 dataChannelWeight;
				sampler2D rayOffset2D;


				uniform fixed hideZeros;
				uniform half leap;
				uniform half maxSamples;
				uniform half maxSlices;
				uniform half contrast;
				uniform half brightness;
				uniform half opacity;
				uniform half valueRangeMin;
				uniform half valueRangeMax;
				uniform half cutValueRangeMin;
				uniform half cutValueRangeMax;


				inline half luminance(half3 rgb)
				{
					//return dot(rgb, half3(0.2126, 0.7152, 0.0722));
					return max(max(rgb.r, rgb.g), rgb.b);
				}

				// cubeIntersection(rayOrigin, rayDirection, float3(0.5, 0.5, 0.5), float3(-0.5, -0.5, -0.5), tNear, tFar);
				// ray-cube intersetion, tNear and tFar can be see in article "Fast, Branchless Ray/Bounding Box Intersetions, Part 1" in Evernote.
				fixed cubeIntersection(float3 origin, float3 direction, float3 aabbMax, float3 aabbMin, out float tNear, out float tFar)
				{
					float3 invDir = 1.0 / direction;
					float3 t1 = invDir * (aabbMax - origin);
					float3 t2 = invDir * (aabbMin - origin);
					float3 tMin = min(t1, t2);
					float3 tMax = max(t1, t2);
					tNear = max(max(tMin.x, tMin.y), tMin.z);
					tFar = min(min(tMax.x, tMax.y), tMax.z);
					return tNear <= tFar;
				}

				half4 getColor(float3 pos)
				{
					half4 data = tex3Dlod(data3D, float4(pos.xyz, 0)) * dataChannelWeight;
//#ifdef VOLUMEVIEWER_RGBA_DATA
//					half intensity = luminance(data.rgb);
//#else
					half intensity = data.r + data.a;
					data.rgb = intensity;
					data.a = 1.0;
//#endif
					half intensityScaled = (intensity - valueRangeMin) / (valueRangeMax - valueRangeMin);
					intensityScaled *= (intensityScaled >= cutValueRangeMin && intensityScaled <= cutValueRangeMax);
					intensityScaled = saturate(intensityScaled);
					data.rgb *= intensityScaled / max(EPSILON_HALF, intensity);

					return saturate(data);
				}

				v2f vert(u2v v)
				{
					v2f o;
					o.pos = mul(UNITY_MATRIX_MVP, v.pos);
					o.osPos = v.pos.xyz;
					o.uv = v.uv;
					return o;
				}

				half4 frag(v2f i) : COLOR 
				{
					//得到ray的起始位置，考虑了camera本身的near clip
					float3 rayOrigin = _WorldSpaceCameraPos.xyz;
					rayOrigin = mul(unity_WorldToObject, float4(rayOrigin, 1)).xyz;
					float3 rayDirection = normalize(i.osPos - rayOrigin);
					rayOrigin += rayDirection * nearClipPlane;

					//得到与cube的交错点的位置作为ray的开始和结束, 这里因为ray使用的是本地坐标，所以不管原先的cube怎么旋转
					//cube的所有的顶点正好符合AABB的结构
					//又因为在frag中，所以这段代码一定是因为camera能看到，也就是射线必定跟cube的要渲染的部分交错
	    			float tNear, tFar;
					cubeIntersection(rayOrigin, rayDirection, float3(0.5, 0.5, 0.5), float3(-0.5, -0.5, -0.5), tNear, tFar);
					tNear *= tNear >= 0;
	    			float3 startRay = rayOrigin + rayDirection * tNear;
	    			float3 endRay = rayOrigin + rayDirection * tFar;

	    			//wha't this? seems the divide function isn't useful after test
					float2 uv = i.pos.xy; /// _ScreenParams.xy;

	    			startRay =  startRay + 0.5;
	    			endRay =  endRay + 0.5;
					float maxRayLength = length(endRay - startRay);
					float rayLengthStep = 1.732 / maxSamples;
					//rayoffset用来消除摩尔纹
					float rayOffset = (tex2D(rayOffset2D, i.uv).r - 0.5) * rayLengthStep;
					float rayLength = rayOffset;

					half4 dst = 0;
					float opacityCorrection = maxSlices / maxSamples;

					half speed = max(1, ceil(maxSamples * leap / maxSlices));
					half skipProtection = 0;
					half samples = 0;
					[loop]
					while(rayLength < maxRayLength)
					{
						half4 src = getColor(startRay + rayLength * rayDirection);
						half intensity = luminance(src.rgb)*src.a;

						if (speed > 1.01 && intensity > SKIP_INTENSITY)
						{
							skipProtection = speed - 1;
							rayLength -= rayLengthStep * skipProtection;
							speed = 1;
							continue;
						}
						else if (speed < 1.01 && intensity <= SKIP_INTENSITY && skipProtection < 0.01)
						{
							speed = max(1, ceil(maxSamples * leap / maxSlices));
						}
						else if(skipProtection > 0.01)
						{
							skipProtection -= 1;
						}
						src.a *= (hideZeros)*(intensity > 0) + (1-hideZeros);
						src.a *= opacity;
						src.rgb = contrast*(src.rgb - 0.5) + 0.5 + brightness;

						src.a = (1.0 - pow((1.0 - src.a), opacityCorrection));

						src.rgb *= src.a;
						dst = (1.0f - dst.a) * src + dst;
						if (dst.a >= 0.997)
						{
							dst.a = 1.0;
							break;
						}
						rayLength += rayLengthStep * speed;
						samples += 1;
						//Failsafe
						if (samples > maxSamples)
						{
							break;
						}
					}
					return dst;
				}
			ENDCG
		}
	}
	FallBack Off
}